package com.cloud.base.utils;

import com.cloud.base.annotation.NeedSetValue;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author liuyuan
 */
@Component
public class BuildReturnDataUtils implements ApplicationContextAware {

    private static Logger logger = LoggerFactory.getLogger(BuildReturnDataUtils.class);

    private static final Map<String, Object> cacheMap = new HashMap<>();

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (this.applicationContext == null) {
            this.applicationContext = applicationContext;
        }
    }

    public void setBeansFiled(Collection coll) throws Exception {
        Class<?> aClass = coll.iterator().next().getClass();
        Field[] fields = aClass.getDeclaredFields();
        for (Field needSetValue : fields) {
            if (needSetValue.getAnnotation(NeedSetValue.class) == null) {
                continue;
            }
            needSetValue.setAccessible(true);
            NeedSetValue nv = needSetValue.getAnnotation(NeedSetValue.class);
            Object bean = null;
            //todo method 做缓存
            Method method = null;
            if (StringUtils.isEmpty(nv.ref())) {
                bean = this.applicationContext.getBean(nv.beanClass());
                method = getUseMethod(nv);
            }
            Object args;
            Field paramField = null;
            if (StringUtils.isNotEmpty(nv.param())) {
                paramField = aClass.getDeclaredField(nv.param());
                paramField.setAccessible(true);
            }
          //  String keyPreFix = nv.beanClass() + "-" + nv.method() + "-" + nv.value();
            for (Object col : coll) {
                Object value;
                if (paramField != null) {
                    args = paramField.get(col);
                    //String key = keyPreFix + args;
                    String key = nv.id() + "-" + args;
                    if (cacheMap.containsKey(nv.ref() + "-" + args)) {
                        value = cacheMap.get(nv.ref() + "-" + args);
                    } else {
                        value = getValue(method, bean, args);
                        //cacheMap.put(nv.id() + "-" + args, value);
                        cacheMap.put(key, value);
                    }
                    setValue(value, col, nv, aClass);
                } else {
                    value = getValue(method, bean, null);
                    setValue(value, col, nv, aClass);
                }
            }

        }
    }

    public void setBeanFiled(Object obj) throws Exception {
        Class<?> aClass = obj.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field needSetValue : declaredFields) {
            NeedSetValue nv = needSetValue.getAnnotation(NeedSetValue.class);
            if (nv == null) {
                continue;
            }
            needSetValue.setAccessible(true);
            Object args = null;
            if (StringUtils.isNotEmpty(nv.param())) {
                Field paramField = aClass.getDeclaredField(nv.param());
                paramField.setAccessible(true);
                args = paramField.get(obj);
            }
            Object value;
            if (StringUtils.isNotEmpty(nv.ref()) && StringUtils.isEmpty(nv.id())) {
                value = cacheMap.get(nv.ref());
                setValue(value, obj, nv, aClass);
                continue;
            }
            Object bean = this.applicationContext.getBean(nv.beanClass());
            Method method = getUseMethod(nv);
            if (StringUtils.isNotEmpty(nv.id())) {
                value = getValue(method, bean, args);
                cacheMap.put(nv.id(), value);
                setValue(value, obj, nv, aClass);
            } else {
                value = getValue(method, bean, args);
                setValue(value, obj, nv, aClass);
            }


        }
    }

    private Method getUseMethod(NeedSetValue nv) throws Exception {
        Method useMethod = null;
        Method[] methods = nv.beanClass().getMethods();
        for (Method method : methods) {
            String name = method.getName();
            if (name.equals(nv.method())) {
                useMethod = method;
                break;
            }
        }
        if (useMethod == null) {
            throw new Exception("没有此方法");
        }
        return useMethod;
    }

    private Object getFieldValue(Object bean, NeedSetValue nv) throws Exception {
        Field value = bean.getClass().getDeclaredField(nv.value());
        value.setAccessible(true);
        return value.get(bean);
    }

    private Object getValue(Method method, Object bean, Object args) throws Exception {
        //TODO 可以添加缓存以减少查询数据库
        if (args == null) {
            try {
                return method.invoke(bean);
            } catch (IllegalAccessException e) {
                //TODO mybatis plus生成接口 调用重载导致的异常
                return method.invoke(bean, null);
            } catch (IllegalArgumentException e) {
                logger.warn("调用重载导致的异常:可忽略。");
            } catch (InvocationTargetException e) {

            }
        }
        return method.invoke(bean, args);
    }

    private void setValue(Object value, Object col, NeedSetValue nv, Class<?> aClass) throws Exception {
        Field needSetValue = aClass.getDeclaredField(nv.value());
        needSetValue.setAccessible(true);
        if (value != null) {
            boolean isObject = nv.isObject();
            if (isObject) {
                Object bean;
                List<Object> objects = new ArrayList<>();
                Class<?> aClazz = needSetValue.getType();
                if (value instanceof Collection) {
                    Collection needValue = (Collection) value;
                    objects.add(needValue);
                    bean = objects;
                } else {
                    bean = aClazz.newInstance();
                    BeanUtils.copyProperties(value, bean);
                }
                needSetValue.set(col, bean);
            } else {
                Object fieldValue = getFieldValue(value, nv);
                needSetValue.set(col, fieldValue);
            }
        }
    }
}
